import puppeteer from 'puppeteer'
// import { getPuppetterExecutablePath } from '../lib/helper'
const defaultBorwserConfig = {
    // 以 无头模式（隐藏浏览器界面）运行浏览器
    headless: true,
    // 设置该参数能解决网站证书无效问题
    ignoreHTTPSErrors: true,
    // 采用了配置方式配置
    // executablePath: getPuppetterExecutablePath(),
    args: [
        '--disable-gpu', // GPU硬件加速
        '--disable-dev-shm-usage', // 创建临时文件共享内存
        '--disable-setuid-sandbox', // uid沙盒
        '--no-first-run', // 没有设置首页。在启动的时候，就会打开一个空白页面。
        '--no-sandbox', // 沙盒模式
        '--no-zygote',
        '--single-process', // 单进程运行
    ],
}
class Poppeteer {
    constructor() {
        this._max_browser = 5;
        this._browser_list = new Map();
        this._browser_page_list = new Map();
        this._init()
    }

    /**
     * 截图
     * @param {
     * url string 请求地址
     * width string 视口宽度
     * height string 视口高度
     * waitTimeout number 等待渲染超时时间
     * selector string 选择器、类和id
     * headers Record<string, string> 设置header键值对
     * } param0 
     * @returns 
     */
    async screenshot({
        url,
        width = 375,
        height = 677,
        waitTimeout = 5000,
        selector,
        encoding,
        headers
    }) {
        const { page, loadLevelId } = await this._openBrowserPage()
        try {
            await page.setViewport({ width, height });
            headers && (await page.setExtraHTTPHeaders(headers));
            await page.goto(url, {
                // 不再有网络连接时触发（至少500毫秒后）,认为页面跳转完成
                waitUtil: 'networkidle0',
            });
            // 加载等待页面加载完成
            await this._waitRender(page, waitTimeout)

            const img = await this._screenshot({ page, selector, encoding })
            return img
        } catch (e) {
            this._error('执行剪切错误信息', e)
        } finally {
            await page.close()
            this._browserPageTabSet(loadLevelId, false)
        }
    }

    async _openBrowserPage() {
        const [browser, loadLevelId] = await this._getCurrentBrowser()
        const page = await browser.newPage();
        this._browserPageTabSet(loadLevelId, true)
        return {
            loadLevelId,
            page
        }
    }

    async _screenshot({ page, selector, encoding }) {
        let shotPage = page
        let config = { type: 'png' }
        if (encoding) {
            config.encoding = encoding
        }
        if (selector) {
            const element = await page.$(selector)
            if (element) {
                shotPage = element
                const boundingBox = await element.boundingBox();
                config.clip = boundingBox
            }
        } else {
            config.fullPage = true
        }
        const img = await shotPage.screenshot(config)
        return img
    }

    _init() {
        (async () => {
            this._info('config:', defaultBorwserConfig)
            for (let i = 0; i < this._max_browser; i++) {
                await this._generateBrowser(i);
            }
            this._info('_browser_list size or num', this._browser_list, this._browser_page_list)
        })()
    }

    async _generateBrowser(id) {
        // 先通过 puppeteer.launch() 创建一个浏览器实例 Browser 对象
        const browser = await puppeteer.launch(defaultBorwserConfig);
        // 存储浏览器 websocket 的地址
        const wsEndInstance = browser.wsEndpoint()
        this._browser_list.set(id, wsEndInstance)
        // 初始化打开次数,因为浏览器会打开一个空白页
        this._browserPageTabSet(id)

        return browser;
    }

    async _getCurrentBrowser() {
        let sourceBrowser = [...this._browser_page_list.entries()]
        sourceBrowser.sort((a, b) => a[1] - b[1])
        const loadLevelId = sourceBrowser[0][0]

        this._info('当前选择的实例状态', loadLevelId, this._browser_list, this._browser_page_list)
        let browserWSEndpoint = this._browser_list.get(loadLevelId)
        let browser;
        try {
            // 使用节点来重新建立连接
            browser = await puppeteer.connect({ browserWSEndpoint });
        } catch (err) {
            this._error(`当前的游览器实例${loadLevelId}连接失败, 重建中...`)
            // 连接失败重新创建新的浏览器实例
            browser = await this._generateBrowser(loadLevelId);
            this._error(`游览器实例${loadLevelId}重建完成`)
        }
        this._info('当前browser进程ID：', loadLevelId, '打开tab数：', (await browser.pages()).length)

        return [browser, loadLevelId];
    }

    _browserPageTabSet(loadLevelId, isIncrease) {
        let isExistLoadId = this._browser_page_list.has(loadLevelId)
        let count = this._browser_page_list.get(loadLevelId)
        // 增加打开页面次数
        isExistLoadId
            ? this._browser_page_list.set(loadLevelId, isIncrease ? ++count : --count)
            : this._browser_page_list.set(loadLevelId, 1)
    }

    async _waitRender(page, timeout) {
        const renderDoneHandle = await page.waitForFunction('window._puppeteerRenderDone', {
            polling: 120,
            timeout: timeout,
        });

        const renderDone = await renderDoneHandle.jsonValue();
        if (renderDone === true) return true;
        this._info('_waitRender渲染失败')
        await page.close();
        return false
    }

    _info(...args) {
        console.info('[Puppeteer info]  ——————  ', ...args)
    }

    _error(...args) {
        console.error('[Puppeteer error]    ——————  ', ...args)
    }

    static getInstance() {
        return Poppeteer.instance || (Poppeteer.instance = new Poppeteer())
    }
}

module.exports = Poppeteer